// Package storage provides Composefs storage backend implementation
package storage

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/smart-snapshotter/pkg/types"
)

// ComposefsStorage provides Composefs-based storage backend
type ComposefsStorage struct {
	*BaseBackend
	mountPoint   string
	composefsPath string
	overlayPath  string
}

// ComposefsConfig represents Composefs-specific configuration
type ComposefsConfig struct {
	Config
	MountOptions []string
	OverlayOptions []string
	UseOverlay   bool
}

// NewComposefsStorage creates a new Composefs storage backend
func NewComposefsStorage(config ComposefsConfig) (*ComposefsStorage, error) {
	base := NewBaseBackend(config.Config)
	if err := base.Initialize(); err != nil {
		return nil, fmt.Errorf("failed to initialize base backend: %w", err)
	}

	// Create Composefs-specific directories
	composefsDirs := []string{"composefs", "overlays", "workdirs"}
	for _, dir := range composefsDirs {
		path := filepath.Join(base.basePath, dir)
		if err := os.MkdirAll(path, 0755); err != nil {
			return nil, fmt.Errorf("failed to create Composefs directory %s: %w", dir, err)
		}
	}

	storage := &ComposefsStorage{
		BaseBackend:   base,
		composefsPath: filepath.Join(base.basePath, "composefs"),
		overlayPath:   filepath.Join(base.basePath, "overlays"),
		mountPoint:    filepath.Join(base.basePath, "mounts", "composefs"),
	}

	// Check if Composefs is supported
	if err := storage.checkComposefsSupport(); err != nil {
		return nil, fmt.Errorf("Composefs not supported: %w", err)
	}

	return storage, nil
}

// checkComposefsSupport checks if Composefs is supported on the system
func (c *ComposefsStorage) checkComposefsSupport() error {
	// Check if composefs tools are available
	if _, err := exec.LookPath("composefs"); err != nil {
		return fmt.Errorf("composefs tool not found: %w", err)
	}

	// Check if mkcomposefs is available
	if _, err := exec.LookPath("mkcomposefs"); err != nil {
		return fmt.Errorf("mkcomposefs tool not found: %w", err)
	}

	// Check kernel support for overlayfs (Composefs typically uses overlay)
	if err := exec.Command("modprobe", "overlay").Run(); err != nil {
		// Try to check if it's built-in
		if _, err := os.Stat("/sys/module/overlay"); os.IsNotExist(err) {
			return fmt.Errorf("overlay kernel module not available: %w", err)
		}
	}

	return nil
}

// CreateComposefs creates a new Composefs image
func (c *ComposefsStorage) CreateComposefs(ctx context.Context, sourcePath string) error {
	// Create mount point
	if err := os.MkdirAll(c.mountPoint, 0755); err != nil {
		return fmt.Errorf("failed to create mount point: %w", err)
	}

	// Create composefs image from source directory
	composefsImage := filepath.Join(c.composefsPath, "image.composefs")

	args := []string{
		sourcePath,
		composefsImage,
		"--digest-store", filepath.Join(c.composefsPath, "digests"),
	}

	cmd := exec.CommandContext(ctx, "mkcomposefs", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("mkcomposefs failed: %w, output: %s", err, string(output))
	}

	return nil
}

// Mount mounts the Composefs image
func (c *ComposefsStorage) Mount(ctx context.Context, targetPath string) error {
	// Create target directory
	if err := os.MkdirAll(targetPath, 0755); err != nil {
		return fmt.Errorf("failed to create target directory: %w", err)
	}

	// Check if already mounted
	if c.isMounted(targetPath) {
		return nil
	}

	composefsImage := filepath.Join(c.composefsPath, "image.composefs")

	if c.config.UseOverlay {
		// Mount using overlayfs with Composefs as lower layer
		return c.mountOverlay(ctx, targetPath, composefsImage)
	}

	// Mount composefs directly
	return c.mountComposefs(ctx, targetPath, composefsImage)
}

// mountComposefs mounts composefs directly
func (c *ComposefsStorage) mountComposefs(ctx context.Context, targetPath, composefsImage string) error {
	args := []string{
		"-t", "composefs",
		"-o", "ro", // Read-only
	}

	if len(c.config.MountOptions) > 0 {
		args = append(args, "-o", strings.Join(c.config.MountOptions, ","))
	}

	args = append(args, composefsImage, targetPath)

	cmd := exec.CommandContext(ctx, "mount", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("mount composefs failed: %w, output: %s", err, string(output))
	}

	return nil
}

// mountOverlay mounts using overlayfs with composefs as lower layer
func (c *ComposefsStorage) mountOverlay(ctx context.Context, targetPath, composefsImage string) error {
	// Create overlay directories
	upperDir := filepath.Join(c.overlayPath, "upper")
	workDir := filepath.Join(c.basePath, "workdirs", "overlay")

	for _, dir := range []string{upperDir, workDir} {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("failed to create overlay directory %s: %w", dir, err)
		}
	}

	// First mount composefs to a temporary location
	tempMount := filepath.Join(c.basePath, "temp", "composefs")
	if err := os.MkdirAll(tempMount, 0755); err != nil {
		return fmt.Errorf("failed to create temp mount directory: %w", err)
	}
	defer os.RemoveAll(tempMount)

	if err := c.mountComposefs(ctx, tempMount, composefsImage); err != nil {
		return fmt.Errorf("failed to mount composefs to temp location: %w", err)
	}
	defer c.unmountPath(ctx, tempMount)

	// Mount overlayfs
	mountOptions := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s",
		tempMount, upperDir, workDir)

	args := []string{
		"-t", "overlay",
		"-o", mountOptions,
	}

	if len(c.config.OverlayOptions) > 0 {
		args = append(args, "-o", strings.Join(c.config.OverlayOptions, ","))
	}

	args = append(args, "overlay", targetPath)

	cmd := exec.CommandContext(ctx, "mount", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("mount overlay failed: %w, output: %s", err, string(output))
	}

	return nil
}

// Unmount unmounts the Composefs image
func (c *ComposefsStorage) Unmount(ctx context.Context, targetPath string) error {
	if !c.isMounted(targetPath) {
		return nil
	}

	cmd := exec.CommandContext(ctx, "umount", targetPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("umount failed: %w, output: %s", err, string(output))
	}

	return nil
}

// unmountPath unmounts a specific path
func (c *ComposefsStorage) unmountPath(ctx context.Context, path string) error {
	if !c.isMounted(path) {
		return nil
	}

	cmd := exec.CommandContext(ctx, "umount", path)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("umount %s failed: %w, output: %s", path, err, string(output))
	}

	return nil
}

// isMounted checks if a path is mounted
func (c *ComposefsStorage) isMounted(path string) bool {
	// Check /proc/mounts
	data, err := os.ReadFile("/proc/mounts")
	if err != nil {
		return false
	}

	content := string(data)
	return strings.Contains(content, path)
}

// StoreFile stores a file in Composefs (requires overlay)
func (c *ComposefsStorage) StoreFile(ctx context.Context, hash string, data []byte) error {
	if !c.config.UseOverlay {
		return fmt.Errorf("file storage requires overlay mode in Composefs")
	}

	// Store in upper directory of overlay
	upperPath := filepath.Join(c.overlayPath, "upper", hash)

	// Create directory if not exists
	dir := filepath.Dir(upperPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Write file atomically
	tempFile := upperPath + ".tmp"
	if err := os.WriteFile(tempFile, data, 0644); err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}

	// Atomic rename
	if err := os.Rename(tempFile, upperPath); err != nil {
		os.Remove(tempFile) // Clean up temp file
		return fmt.Errorf("failed to rename file: %w", err)
	}

	// Update statistics
	c.UpdateStats(int64(len(data)), 1, 0, 0)

	return nil
}

// LoadFile loads a file from Composefs
func (c *ComposefsStorage) LoadFile(ctx context.Context, hash string) ([]byte, error) {
	// Try upper directory first (overlay mode)
	if c.config.UseOverlay {
		upperPath := filepath.Join(c.overlayPath, "upper", hash)
		if data, err := os.ReadFile(upperPath); err == nil {
			return data, nil
		}
	}

	// Try mounted composefs
	composefsPath := filepath.Join(c.mountPoint, hash)
	if data, err := os.ReadFile(composefsPath); err == nil {
		return data, nil
	}

	return nil, types.ErrStorageNotFound
}

// DeleteFile deletes a file
func (c *ComposefsStorage) DeleteFile(ctx context.Context, hash string) error {
	if !c.config.UseOverlay {
		return fmt.Errorf("file deletion requires overlay mode in Composefs")
	}

	upperPath := filepath.Join(c.overlayPath, "upper", hash)

	info, err := os.Stat(upperPath)
	if err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to stat file: %w", err)
	}

	if err := os.Remove(upperPath); err != nil {
		return fmt.Errorf("failed to delete file: %w", err)
	}

	// Update statistics
	c.UpdateStats(-info.Size(), -1, 0, 0)

	return nil
}

// FileExists checks if a file exists
func (c *ComposefsStorage) FileExists(ctx context.Context, hash string) (bool, error) {
	// Check upper directory first (overlay mode)
	if c.config.UseOverlay {
		upperPath := filepath.Join(c.overlayPath, "upper", hash)
		if _, err := os.Stat(upperPath); err == nil {
			return true, nil
		}
	}

	// Check mounted composefs
	composefsPath := filepath.Join(c.mountPoint, hash)
	if _, err := os.Stat(composefsPath); err == nil {
		return true, nil
	}

	return false, nil
}

// StoreBlock stores a block (not supported in Composefs)
func (c *ComposefsStorage) StoreBlock(ctx context.Context, hash string, data []byte) error {
	return fmt.Errorf("block storage not supported in Composefs backend")
}

// LoadBlock loads a block (not supported in Composefs)
func (c *ComposefsStorage) LoadBlock(ctx context.Context, hash string) ([]byte, error) {
	return nil, fmt.Errorf("block storage not supported in Composefs backend")
}

// DeleteBlock deletes a block (not supported in Composefs)
func (c *ComposefsStorage) DeleteBlock(ctx context.Context, hash string) error {
	return fmt.Errorf("block storage not supported in Composefs backend")
}

// BlockExists checks if a block exists (not supported in Composefs)
func (c *ComposefsStorage) BlockExists(ctx context.Context, hash string) (bool, error) {
	return false, fmt.Errorf("block storage not supported in Composefs backend")
}

// StoreMetadata stores metadata
func (c *ComposefsStorage) StoreMetadata(ctx context.Context, key string, metadata map[string]string) error {
	// Store metadata in a JSON file
	metadataPath := filepath.Join(c.basePath, "metadata", key+".json")

	dir := filepath.Dir(metadataPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create metadata directory: %w", err)
	}

	// Convert to JSON
	jsonData, err := json.Marshal(metadata)
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	if err := os.WriteFile(metadataPath, jsonData, 0644); err != nil {
		return fmt.Errorf("failed to write metadata: %w", err)
	}

	return nil
}

// LoadMetadata loads metadata
func (c *ComposefsStorage) LoadMetadata(ctx context.Context, key string) (map[string]string, error) {
	metadataPath := filepath.Join(c.basePath, "metadata", key+".json")

	data, err := os.ReadFile(metadataPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, types.ErrStorageNotFound
		}
		return nil, fmt.Errorf("failed to read metadata: %w", err)
	}

	var metadata map[string]string
	if err := json.Unmarshal(data, &metadata); err != nil {
		return nil, fmt.Errorf("failed to unmarshal metadata: %w", err)
	}

	return metadata, nil
}

// DeleteMetadata deletes metadata
func (c *ComposefsStorage) DeleteMetadata(ctx context.Context, key string) error {
	metadataPath := filepath.Join(c.basePath, "metadata", key+".json")

	if err := os.Remove(metadataPath); err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to delete metadata: %w", err)
	}

	return nil
}

// GetStats returns Composefs-specific statistics
func (c *ComposefsStorage) GetStats(ctx context.Context) (StorageStats, error) {
	baseStats, err := c.BaseBackend.GetStats(ctx)
	if err != nil {
		return StorageStats{}, err
	}

	// Add Composefs-specific stats
	var stat syscall.Statfs_t
	if err := syscall.Statfs(c.mountPoint, &stat); err == nil {
		baseStats.TotalSize = int64(stat.Blocks) * int64(stat.Bsize)
		baseStats.FreeSize = int64(stat.Bavail) * int64(stat.Bsize)
		baseStats.UsedSize = baseStats.TotalSize - baseStats.FreeSize
	}

	return baseStats, nil
}

// Cleanup performs Composefs-specific cleanup
func (c *ComposefsStorage) Cleanup(ctx context.Context) error {
	// Unmount if mounted
	if err := c.Unmount(ctx, c.mountPoint); err != nil {
		return fmt.Errorf("failed to unmount: %w", err)
	}

	// Clean up overlay directories
	if c.config.UseOverlay {
		upperDir := filepath.Join(c.overlayPath, "upper")
		workDir := filepath.Join(c.basePath, "workdirs", "overlay")

		for _, dir := range []string{upperDir, workDir} {
			if err := os.RemoveAll(dir); err != nil {
				return fmt.Errorf("failed to clean overlay directory %s: %w", dir, err)
			}
			if err := os.MkdirAll(dir, 0755); err != nil {
				return fmt.Errorf("failed to recreate overlay directory %s: %w", dir, err)
			}
		}
	}

	return c.BaseBackend.Cleanup(ctx)
}

// Close closes the Composefs storage
func (c *ComposefsStorage) Close() error {
	// Unmount all mounted paths
	if err := c.Unmount(context.Background(), c.mountPoint); err != nil {
		return fmt.Errorf("failed to unmount on close: %w", err)
	}

	return nil
}